import AVFoundation
import UIKit

// 游戏回调
protocol GameCallback: NSObjectProtocol {
    func onDifficultyUp(_ difficulty: Int)  // 难度增加事件
    func onGameOver(_ isWon: Bool)          // 游戏结束事件
    func onScore(_ score: Int)              // 得分事件
}

// 游戏
class Game: NSObject {
    // 状态
    public enum Status {
        case END        // 已结束
        case PAUSED     // 暂停中
        case PLAYING    // 游戏中
    }

    private let mCallback: GameCallback                 // 回调
    private var mCurBlock: Block!                       // 当前方块
    private var mDifficulty: Int                        // 难度
    private var mEnableMusic: Bool                      // 音乐开关
    private let mGameBoard: UIView                      // 游戏区域
    private let mGameBoardDrawer: CGContext             // 游戏区域绘图者
    private var mMusicPlayer: AVAudioPlayer!            // 音乐播放者
    private var mNextBlock: Block!                      // 下一个方块
    private var mNormalTimer: Timer!                    // 方块正常下落定时器
    private let mPreview: UIView                        // 预览区域
    private let mPreviewDrawer: CGContext               // 预览区域绘图者
    private var mScore: Int                             // 得分
    private var mSpeedUpTimer: Timer!                   // 方块加速下落定时器
    private var mStatus: Status                         // 状态
    private var mTop: Int                               // 有方块的最高行
    public static var sExistentBlocks: [[ColorGrid]]!   // 累积的方块

    /**
     * 构造方法。
     * @param callback          回调
     * @param gameBoard         游戏区域
     * @param gameBoardDrawer   游戏区域绘图者
     * @param preview           预览区域
     * @param previewDrawer     预览区域绘图者
     */
    public init(_ callback: GameCallback, gameBoard: UIView, gameBoardDrawer: CGContext, preview: UIView, previewDrawer: CGContext) {
        // 初始化成员
        mCallback = callback
        mDifficulty = 1
        mEnableMusic = false
        mGameBoard = gameBoard
        mGameBoardDrawer = gameBoardDrawer
        let path = Bundle.main.path(forResource: Const.MUSIC_FILE_NAME, ofType: Const.MUSIC_EXTEND)!
        let url = URL(fileURLWithPath: path)
        do {
            mMusicPlayer = try AVAudioPlayer(contentsOf: url)
            mMusicPlayer.numberOfLoops = -1    // 设置为循环播放
        } catch {
            print(error.localizedDescription)
        }
        mPreview = preview
        mPreviewDrawer = previewDrawer
        mScore = 0
        mStatus = .END
        mTop = Const.GAME_BOARD_GRID_COUNT_V + 1
        super.init()
        
        // 画游戏区域
        self.drawGameBoard()
    
        // 画预览区域
        self.drawPreview()
    }
    
    /**
     * 取消方块加速下落。
     */
    public func cancelDownSpeedUp() {
        if mStatus != .PLAYING {
            return
        }

        mSpeedUpTimer.invalidate()
        mSpeedUpTimer = nil
    }

    /**
     * 检查是否存在满行。
     */
    private func checkRow() {
        // 更新"有方块的最高行"
        if mCurBlock.top < mTop {
            mTop = mCurBlock.top
        }

        var released = 0    // 消除的行数

        // 从有方块的最上面一行，到当前方块所占的最下一行，逐行检查
        for i in mTop ... mCurBlock.bottom {
            for j in 0 ..< Const.GAME_BOARD_GRID_COUNT_H {
                // 如果该行有空的，就跳过该行
                if !Game.sExistentBlocks[i][j].mUsed {
                    break
                }
                // 如果该行满了，消除该行
                if j == Const.GAME_BOARD_GRID_COUNT_H - 1 {
                    self.releaseRow(i)
                    released += 1
                }
            }
        }

        if released > 0 {
            // 因为消行了，所以需要重绘
            self.drawGameBoard()
            self.drawExistentBlock()
            
            // 计算得分
            mScore += released * released * 100

            // 发送通知
            mCallback.onScore(mScore)
            
            // 计算难度
            if mScore / Const.DIFF_BASE + 1 > mDifficulty && mDifficulty < Const.DIFF_MAX {
                // 增加难度
                mDifficulty += 1
                mNormalTimer.invalidate()
                let elapse = Const.TIMER_INIT - Const.TIMER_GROW * Double(mDifficulty - 1)
                mNormalTimer = Timer.scheduledTimer(timeInterval: elapse, target: self, selector: #selector(Game.onTimer), userInfo: nil, repeats: true)
               
                // 发送通知
                mCallback.onDifficultyUp(mDifficulty)
            }
        }
    }

    /**
     * 随机创建一种方块。
     * @return 方块
     */
    private func createRandBlock() -> Block {
        let random = arc4random() % 7 + 1

        switch random {
            case 1:
                return Block1()
            case 2:
                return Block2()
            case 3:
                return Block3()
            case 4:
                return Block4()
            case 5:
                return Block5()
            case 6:
                return Block6()
            case 7:
                return Block7()
            default:
                return Block1()
        }
    }

    /**
     * 方块加速下落。
     */
    public func downSpeedUp() {
        if mStatus != .PLAYING {
            return
        }

        mSpeedUpTimer = Timer.scheduledTimer(timeInterval: Const.TIMER_SPEEDUP, target: self, selector: #selector(Game.onTimer), userInfo: nil, repeats: true)
    }

    /**
     * 画累积的方块。
     */
    private func drawExistentBlock() {
        var rect: CGRect!
        for i in 0 ..< Const.GAME_BOARD_GRID_COUNT_V {
            for j in 0 ..< Const.GAME_BOARD_GRID_COUNT_H {
                if Game.sExistentBlocks[i][j].mUsed {
                    mGameBoardDrawer.setFillColor(Game.sExistentBlocks[i][j].mColor)
                    rect = CGRect(x: Const.GRID_SIZE * j, y: Const.GRID_SIZE * i, width: Const.GRID_SIZE, height: Const.GRID_SIZE)
                    mGameBoardDrawer.fill(rect)
                    Block.drawEdge(mGameBoardDrawer, left: Const.GRID_SIZE * j, top: Const.GRID_SIZE * i)
                }
            }
        }
    }

    /**
     * 画游戏区域。
     */
    private func drawGameBoard() {
        mGameBoardDrawer.setFillColor(Const.GAME_BOARD_COLOR)
        let rect = CGRect(x: 0, y: 0, width: mGameBoard.frame.width, height: mGameBoard.frame.height)
        mGameBoardDrawer.fill(rect)
        mGameBoard.setNeedsDisplay()
    }
    
    /**
     * 画预览区域。
     */
    private func drawPreview() {
        mPreviewDrawer.setFillColor(Const.PREVIEW_COLOR)
        let rect = CGRect(x: 0, y: 0, width: mPreview.frame.width, height: mPreview.frame.height)
        mPreviewDrawer.fill(rect)
        if mNextBlock != nil {
            mNextBlock.enterPreview(mPreview, drawer: mPreviewDrawer)
        }
        mPreview.setNeedsDisplay()
    }

    /**
     * 游戏结束。
     * @param isWon  胜利则为 true，否则为 false
     */
    private func gameOver(_ isWon: Bool) {
        // 将状态设置为已结束
        mStatus = .END

        // 停止定时器
        mNormalTimer.invalidate()
        if mSpeedUpTimer != nil {
            mSpeedUpTimer.invalidate()
            mSpeedUpTimer = nil
        }
        
        // 停止播放音乐
        if mEnableMusic {
            mMusicPlayer.stop()
        }

        // 发送通知
        mCallback.onGameOver(isWon)
    } 
       
    /**
     * 判断是否失败。
     * @return 失败则返回 true，否则返回 false
     */
    private func isLost() -> Bool {
        return mTop == 0 && Game.sExistentBlocks[0][4].mUsed
    }
    
    /**
     * 判断是否胜利。
     * @return 胜利则返回 true，否则返回 false
     */
    private func isWon() -> Bool {
        return mScore >= Const.WIN_NUMBER
    }

    /**
     * 方块下落。
     */
    public func moveBlockDown() {
        if mStatus != .PLAYING {
            return
        }

        let canMoveDown = mCurBlock.moveDown()
        if !canMoveDown {
            // 检查是否存在满行
            self.checkRow()
            
            // 判断是否胜利
            if self.isWon() {
                self.gameOver(true)
                return
            }

            // 判断是否失败
            if self.isLost() {
                self.gameOver(true)
                return
            }

            // 产生新的方块
            self.nextBlock()
        }
    }
    
    /**
     * 方块左移。
     */
    public func moveBlockLeft() {
        if mStatus == .PLAYING {
            mCurBlock.moveLeft()
        }
    }
    
    /**
     * 方块右移。
     */
    public func moveBlockRight() {
        if mStatus == .PLAYING {
            mCurBlock.moveRight()
        }
    }

    /**
     * 产生新的方块。
     */
    private func nextBlock() {
        // 创建当前方块
        if mCurBlock != nil {
            mCurBlock = mNextBlock
        } else {
            mCurBlock = self.createRandBlock()
        }

        // 创建下一个方块
        mNextBlock = self.createRandBlock()

        // 当前方块进入游戏区域
        mCurBlock.enterGameBoard(mGameBoard, drawer: mGameBoardDrawer)

        // 下一个方块进入预览区域
        self.drawPreview()
    }

    /**
     * 定时器事件的响应方法。
     */
    @objc public func onTimer() {
        self.moveBlockDown()
    }
   
    /**
     * 游戏暂停。
     */
    public func pause() {
        if mStatus == .PLAYING {
            // 停止定时器
            mNormalTimer.invalidate()
            if mSpeedUpTimer != nil {
                mSpeedUpTimer.invalidate()
                mSpeedUpTimer = nil
            }
            
            // 停止播放音乐
            if mEnableMusic {
                mMusicPlayer.pause()
            }

            // 将状态设置为已暂停
            mStatus = .PAUSED
        } else if mStatus == .PAUSED {
            // 开启定时器
            let elapse = Const.TIMER_INIT - Const.TIMER_GROW * Double(mDifficulty - 1)
            mNormalTimer = Timer.scheduledTimer(timeInterval: elapse, target: self, selector: #selector(Game.onTimer), userInfo: nil, repeats: true)
            
            // 播放音乐
            if mEnableMusic {
                mMusicPlayer.play()
            }
    
            // 将状态设置为游戏中
            mStatus = .PLAYING
        }
    }
    
    /**
     * 播放音乐。
     * @return 正在播放音乐则为 true，否则为 false
     */
    public func playMusic() -> Bool {
        mEnableMusic = !mEnableMusic
    
        if mEnableMusic {
            if mStatus == .PLAYING {
                mMusicPlayer.play()
            }
        } else {
            mMusicPlayer.pause()
        }
    
        return mEnableMusic
    }

    /**
     * 消行。
     * @param row   行号
     */
    private func releaseRow(_ row: Int) {
        // 最上行
        if row == 0 {
            for j in 0 ..< Const.GAME_BOARD_GRID_COUNT_H {
                Game.sExistentBlocks[row][j].mUsed = false
            }
            mTop += 1
            return
        }

        // 非最上行
        for i in (mTop + 1 ... row).reversed() {
            for j in 0 ..< Const.GAME_BOARD_GRID_COUNT_H {
                Game.sExistentBlocks[i][j].mUsed = Game.sExistentBlocks[i - 1][j].mUsed
                Game.sExistentBlocks[i][j].mColor = Game.sExistentBlocks[i - 1][j].mColor
            }
        }

        for j in 0 ..< Const.GAME_BOARD_GRID_COUNT_H {
            Game.sExistentBlocks[mTop][j].mUsed = false
        }

        mTop += 1
    }
    
    /**
     * 游戏开始。
     */
    public func start() {
        // 画游戏区域
        self.drawGameBoard()

        // 画预览区域
        self.drawPreview()

        // 创建方块
        Game.sExistentBlocks = [[ColorGrid]]()
        var colorGridArray: Array<ColorGrid>!
        for _ in 0 ..< Const.GAME_BOARD_GRID_COUNT_V {
            colorGridArray = Array<ColorGrid>()
            for _ in 0 ..< Const.GAME_BOARD_GRID_COUNT_H {
                colorGridArray.append(ColorGrid())
            }
            Game.sExistentBlocks.append(colorGridArray)
        }
        mCurBlock = nil
        mNextBlock = nil
        self.nextBlock()

        // 重置难度
        mDifficulty = 1

        // 重置分数
        mScore = 0
    
        // 开启定时器
        if mTimer != nil {
            mTimer.invalidate()
        }
        mNormalTimer = Timer.scheduledTimer(timeInterval: Const.TIMER_INIT, target: self, selector: #selector(Game.onTimer), userInfo: nil, repeats: true)
        
        // 播放音乐
        if mEnableMusic {
            mMusicPlayer.play()
        }
        
        // 将状态设置为游戏中
        mStatus = .PLAYING
    }
    
    /**
     * 获取游戏状态。
     */
    public var status: Status {
        get {
            return mStatus
        }
    }

    /**
     * 方块变形。
     */
    public func transformBlock() {
        if mStatus == .PLAYING {
            mCurBlock.transform()
        }
    }
}
